/*
 * Vulkan Samples
 *
 * Copyright (C) 2015-2016 Valve Corporation
 * Copyright (C) 2015-2016 LunarG, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
VULKAN_SAMPLE_SHORT_DESCRIPTION
Draw Cube
*/

/* This is part of the draw cube progression */

#include <util_init.hpp>
#include <assert.h>
#include <string.h>
#include <cstdlib>
#include "SampleBase.hpp"
#include "cube_data.h"

/* For this sample, we'll start with GLSL so the shader function is plain */
/* and then use the glslang GLSLtoSPV utility to convert it to SPIR-V for */
/* the driver.  We do this for clarity rather than using pre-compiled     */
/* SPIR-V                                                                 */

static const char *vertShaderText =
    "#version 400\n"
    "#extension GL_ARB_separate_shader_objects : enable\n"
    "#extension GL_ARB_shading_language_420pack : enable\n"
    "layout (std140, binding = 0) uniform bufferVals {\n"
    "    mat4 mvp;\n"
    "} myBufferVals;\n"
    "layout (location = 0) in vec4 pos;\n"
    "layout (location = 1) in vec4 inColor;\n"
    "layout (location = 0) out vec4 outColor;\n"
    "void main() {\n"
    "   outColor = inColor;\n"
    "   gl_Position = myBufferVals.mvp * pos;\n"
    "}\n";

static const char *fragShaderText =
    "#version 400\n"
    "#extension GL_ARB_separate_shader_objects : enable\n"
    "#extension GL_ARB_shading_language_420pack : enable\n"
    "layout (location = 0) in vec4 color;\n"
    "layout (location = 0) out vec4 outColor;\n"
    "void main() {\n"
    "   outColor = color;\n"
    "}\n";

class Sample : public SampleBase
{
public:
	Sample(struct sample_info& info) : SampleBase(info) 
	{
		camera.init(glm::vec3(0, 0, 8),
			        glm::vec3(),
			        0.5f,
			        0.5f);
	};

	~Sample() {};

	void init()
	{
		char sample_title[] = "Draw Cube";
		const bool depthPresent = true;

		init_global_layer_properties(info);
		init_instance_extension_names(info);
		init_device_extension_names(info);
		init_instance(info, sample_title);
		init_enumerate_device(info);
		init_window_size(info, 500, 500);
		init_connection(info);
		init_window(info); SetWindowLongPtr(info.window, GWLP_USERDATA, (LONG_PTR)this);
		init_swapchain_extension(info);
		init_device(info);

		init_command_pool(info);
		init_command_buffer(info);
		init_device_queue(info);
		init_swap_chain(info);
		init_depth_buffer(info);
		init_uniform_buffer(info);
		init_descriptor_and_pipeline_layouts(info, false);
		init_renderpass(info, depthPresent);
		init_shaders(info, vertShaderText, fragShaderText);
		init_framebuffers(info, depthPresent);
		init_vertex_buffer(info, g_vb_solid_face_colors_Data, sizeof(g_vb_solid_face_colors_Data),
			sizeof(g_vb_solid_face_colors_Data[0]), false);
		init_descriptor_pool(info, false);
		init_descriptor_set(info, false);
		init_pipeline_cache(info);
		init_pipeline(info, depthPresent);
	}

	void render()
	{
		VkResult U_ASSERT_ONLY res;

		/* VULKAN_KEY_START */

		VkClearValue clear_values[2];
		clear_values[0].color.float32[0] = 0.2f;
		clear_values[0].color.float32[1] = 0.2f;
		clear_values[0].color.float32[2] = 0.2f;
		clear_values[0].color.float32[3] = 0.2f;
		clear_values[1].depthStencil.depth = 1.0f;
		clear_values[1].depthStencil.stencil = 0;

		VkSemaphore imageAcquiredSemaphore;
		VkSemaphoreCreateInfo imageAcquiredSemaphoreCreateInfo;
		imageAcquiredSemaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
		imageAcquiredSemaphoreCreateInfo.pNext = NULL;
		imageAcquiredSemaphoreCreateInfo.flags = 0;

		res = vkCreateSemaphore(info.device, &imageAcquiredSemaphoreCreateInfo, NULL, &imageAcquiredSemaphore);
		assert(res == VK_SUCCESS);

		// Get the index of the next available swapchain image:
		res = vkAcquireNextImageKHR(info.device, info.swap_chain, UINT64_MAX, imageAcquiredSemaphore, VK_NULL_HANDLE,
			&info.current_buffer);
		// TODO: Deal with the VK_SUBOPTIMAL_KHR and VK_ERROR_OUT_OF_DATE_KHR
		// return codes
		assert(res == VK_SUCCESS);

		execute_begin_command_buffer(info);

		VkRenderPassBeginInfo rp_begin;
		rp_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
		rp_begin.pNext = NULL;
		rp_begin.renderPass = info.render_pass;
		rp_begin.framebuffer = info.framebuffers[info.current_buffer];
		rp_begin.renderArea.offset.x = 0;
		rp_begin.renderArea.offset.y = 0;
		rp_begin.renderArea.extent.width = info.width;
		rp_begin.renderArea.extent.height = info.height;
		rp_begin.clearValueCount = 2;
		rp_begin.pClearValues = clear_values;

		vkCmdBeginRenderPass(info.cmd, &rp_begin, VK_SUBPASS_CONTENTS_INLINE);

		vkCmdBindPipeline(info.cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, info.pipeline);
		vkCmdBindDescriptorSets(info.cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, info.pipeline_layout, 0, NUM_DESCRIPTOR_SETS,
			info.desc_set.data(), 0, NULL);

		const VkDeviceSize offsets[1] = { 0 };
		vkCmdBindVertexBuffers(info.cmd, 0, 1, &info.vertex_buffer.buf, offsets);

		init_viewports(info);
		init_scissors(info);

		vkCmdDraw(info.cmd, 12 * 3, 1, 0, 0);
		vkCmdEndRenderPass(info.cmd);

		execute_end_command_buffer(info);

		const VkCommandBuffer cmd_bufs[] = { info.cmd };
		VkFenceCreateInfo fenceInfo;
		VkFence drawFence;
		fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
		fenceInfo.pNext = NULL;
		fenceInfo.flags = 0;
		vkCreateFence(info.device, &fenceInfo, NULL, &drawFence);

		VkPipelineStageFlags pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
		VkSubmitInfo submit_info[1] = {};
		submit_info[0].pNext = NULL;
		submit_info[0].sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
		submit_info[0].waitSemaphoreCount = 1;
		submit_info[0].pWaitSemaphores = &imageAcquiredSemaphore;
		submit_info[0].pWaitDstStageMask = &pipe_stage_flags;
		submit_info[0].commandBufferCount = 1;
		submit_info[0].pCommandBuffers = cmd_bufs;
		submit_info[0].signalSemaphoreCount = 0;
		submit_info[0].pSignalSemaphores = NULL;

		/* Queue the command buffer for execution */
		res = vkQueueSubmit(info.graphics_queue, 1, submit_info, drawFence);
		assert(res == VK_SUCCESS);

		/* Now present the image in the window */

		VkPresentInfoKHR present;
		present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
		present.pNext = NULL;
		present.swapchainCount = 1;
		present.pSwapchains = &info.swap_chain;
		present.pImageIndices = &info.current_buffer;
		present.pWaitSemaphores = NULL;
		present.waitSemaphoreCount = 0;
		present.pResults = NULL;

		/* Make sure command buffer is finished before presenting */
		do {
			res = vkWaitForFences(info.device, 1, &drawFence, VK_TRUE, FENCE_TIMEOUT);
		} while (res == VK_TIMEOUT);

		assert(res == VK_SUCCESS);
		res = vkQueuePresentKHR(info.present_queue, &present);
		assert(res == VK_SUCCESS);

		vkDestroySemaphore(info.device, imageAcquiredSemaphore, NULL);
		vkDestroyFence(info.device, drawFence, NULL);
	}

	void destroy()
	{
		/* VULKAN_KEY_END */
		if (info.save_images) write_ppm(info, "15-draw_cube");

		destroy_pipeline(info);
		destroy_pipeline_cache(info);
		destroy_descriptor_pool(info);
		destroy_vertex_buffer(info);
		destroy_framebuffers(info);
		destroy_shaders(info);
		destroy_renderpass(info);
		destroy_descriptor_and_pipeline_layouts(info);
		destroy_uniform_buffer(info);
		destroy_depth_buffer(info);
		destroy_swap_chain(info);
		destroy_command_buffer(info);
		destroy_command_pool(info);
		destroy_device(info);
		destroy_window(info);
		destroy_instance(info);
	}

	void update(float deltaTime)
	{
		updateUniformBuffers();
	}

	void updateUniformBuffers()
	{
		VkResult U_ASSERT_ONLY res;

		info.View = camera.viewMat;

		info.MVP = info.Clip * info.Projection * info.View * info.Model;

		uint8_t *pData;
		res = vkMapMemory(info.device, info.uniform_data.mem, 0, VK_WHOLE_SIZE, 0, (void **)&pData);
		assert(res == VK_SUCCESS);

		memcpy(pData, &info.MVP, sizeof(info.MVP));

		vkUnmapMemory(info.device, info.uniform_data.mem);
	}

private:

};

int sample_main(int argc, char *argv[]) {
    struct sample_info info = {};
	process_command_line_args(info, argc, argv);

	Sample* sample = new Sample(info);

	sample->init();
	sample->renderLoop();
	sample->destroy();

	delete sample;
    return 0;
}